/**
 * Class: I_Gotta_Feeling
 *
 * Purpose:
 *
 * Created: Jan 20, 2010 @ 11:42:18 AM
 * @author Ken Scott
 */
package org.openfantasia.demo;

import org.openfantasia.ps.Space;
import org.openfantasia.ps.actor.Actor;
import org.openfantasia.ps.modifier.Behavior;
import org.openfantasia.ps.particle.Particle;
import org.openfantasia.ps.shape.Cube;
import org.openfantasia.ps.shape.Pyramid;
import org.openfantasia.songanalyst.SongObserver;
import org.openfantasia.ui.PColor;
import org.openfantasia.util.listener.Event;
import org.openfantasia.util.listener.FloatScalar;
import org.openfantasia.util.math.DPoint3;
import org.openfantasia.util.math.PMath;

import java.util.ArrayList;
import java.util.List;

public class I_Gotta_Feeling extends SongObserver {

	private static final String AUDIO_FILE = "C:/ART TOOLS/JavaProcessing/data/bep-i-gotta-feeling.mp3";

	public static final String VIDEO_OUTPUT_DIRECTORY = "C:/ART TOOLS/JavaProcessing/video/";

	private static final String VIDEO_OUTPUT_FILE = null;	//VIDEO_OUTPUT_DIRECTORY + "i-gotta-feeling-" + DateTime.COMPACT_DATE_TIME_FORMAT.format(new Date()) + ".mov";

	private static final String EVENT_RECORDING_FILE = null; //"C:/ART TOOLS/JavaProcessing/data/output/i-gotta-feeling-" + DateTime.FULL_DATE_TIME_FORMAT.format(new Date()) + ".evt";

	private static final String EVENT_PLAYBACK_FILE = null;//"C:/ART TOOLS/JavaProcessing/data/output/i-gotta-feeling.evt";

	private static final boolean LOCK_CAMERA = false;

	private static final boolean PRESENT = false;

	private static final double START_TIME = 0;

	private static final int PARTICLE_SIZE = 30;

	private static final int ROW_SIZE = 5;

	private static final int EXTENT = 2000;


	public static void main(String args[]) {
		if (PRESENT) {
			SongObserver.main(new String[]{"--present", I_Gotta_Feeling.class.getName()});
		}
		else {
			SongObserver.main(new String[]{"--location=400,300", I_Gotta_Feeling.class.getName()});
		}
	}


	public static final double[] TIMELINE = {
			0.509545,	// first beat
			0.978086,	// second beat
			1.446786,	// third beat
			1.915529,	// fourth beat
			15.508316,	// first strings
			29.570767,	// first vocals
			60.508559,	// first bass
			90.512918,	// first deep thump
			105.000000,	// first break
			106.251235,	// fergie solo
			116.289127,	// reverb start
			119.154633,	// first 'ch'
			120.032803,	// second 'ch'
			120.954633,	// third 'ch'
			127.583306,	// take it *OFF*
			131.756909, // shut it DOWN
			150.553404,	// guitar riff
			180.747988,	// tonites the night
			181.914715,	// hey!
			195.000000,	// CAR!
			197.840287,	// Mazel Tov!
			198.694686,	// Lakhaim!
			208.163323, // wooooah arp
			211.213123,	// lets do it again
			225.508028,	// here we come, here we go
			240.509594,	// monday
			241.540776,	// tuesday
			242.372328,	// wednesday
			243.322808,	// thursday
			244.257790,	// friday
			245.194839,	// saturday
			246.128789,	// saturday2
			247.071004,	// sunday
			255.275090,	// and i'm ^feeling
			290.000000,	// end of song
	};

	public static final double BEAT_PERIOD = 0.4687;

	private static int TX = 0;

	public static final double FIRST_BEAT = TIMELINE[TX++];

	public static final double SECOND_BEAT = TIMELINE[TX++];

	public static final double THIRD_BEAT = TIMELINE[TX++];

	public static final double FOURTH_BEAT = TIMELINE[TX++];

	public static final double FIRST_STRINGS = TIMELINE[TX++];

	public static final double FIRST_VOCALS = TIMELINE[TX++];

	public static final double FIRST_BASS = TIMELINE[TX++];

	public static final double DEEP_THUMP = TIMELINE[TX++];

	public static final double FIRST_BREAK = TIMELINE[TX++];

	public static final double FERGIE_SOLO = TIMELINE[TX++];

	public static final double FERGIE_REVERB = TIMELINE[TX++];

	public static final double FIRST_CH = TIMELINE[TX++];

	public static final double SECOND_CH = TIMELINE[TX++];

	public static final double THIRD_CH = TIMELINE[TX++];

	public static final double TAKE_IT_OFF = TIMELINE[TX++];

	public static final double SHUT_IT_DOWN = TIMELINE[TX++];

	public static final double GUITAR_RIFF = TIMELINE[TX++];

	public static final double TONITES_THE_NIGHT = TIMELINE[TX++];

	public static final double HEY = TIMELINE[TX++];

	public static final double CAR = TIMELINE[TX++];

	public static final double MAZEL_TOV = TIMELINE[TX++];

	public static final double LAKHAIM = TIMELINE[TX++];

	public static final double WOOO_ARP = TIMELINE[TX++];

	public static final double LETS_DO_IT_AGAIN = TIMELINE[TX++];

	public static final double HERE_WE_COME = TIMELINE[TX++];

	public static final double MONDAY = TIMELINE[TX++];

	public static final double TUESDAY = TIMELINE[TX++];

	public static final double WEDNESDAY = TIMELINE[TX++];

	public static final double THURSDAY = TIMELINE[TX++];

	public static final double FRIDAY = TIMELINE[TX++];

	public static final double SATURDAY = TIMELINE[TX++];

	public static final double SATURDAY2 = TIMELINE[TX++];

	public static final double SUNDAY = TIMELINE[TX++];

	public static final double AND_IM_FEELING = TIMELINE[TX++];

	public static final double END_OF_SONG = TIMELINE[TX++];

	// FIELDS

	private Space space = new Space(this);


	public void setup() {
		int dv = 80;
		if (PRESENT) {
			dv = 150;
		}
		super.setup(16 * dv, 9 * dv, VIDEO_OUTPUT_FILE, OPENGL, EVENT_PLAYBACK_FILE == null, 30.0, AUDIO_FILE, null, 0.0);
		perspective();
		textFont(createFont("SanSerif", 12));

		setSkewTime(FIRST_BEAT);
		setStartTime(START_TIME);
		setEndTime(END_OF_SONG);

		if (EVENT_PLAYBACK_FILE != null) {
			startEventPlayback(EVENT_PLAYBACK_FILE);
		}
		else if (EVENT_RECORDING_FILE != null) {
			startEventRecording(EVENT_RECORDING_FILE);
		}

		final List<Actor> renderers = new ArrayList<Actor>();
		Pyramid pyramidActor = new Pyramid(this) {

			private double power = 0;


			public void setPower(double power) {
				this.power = power;
			}


			public void receive(Event message) {
				if (message.getChannel().equals(LIVE_AVERAGE)) {
					setPower(((FloatScalar) message).getValue());
				}
			}


			public void setup() {

				addBehavior(Behavior.FILL_COLOR, new Behavior(null, null) {
					public Object getValue(Particle particle, Double time, Object param) {
						double siz = power / 8;
						return new PColor(siz, 0.5, 0.5, 1);
					}
				});

				addBehavior(Behavior.LINE_COLOR, new Behavior(FIRST_BASS, null) {
					public Object getValue(Particle particle, Double time, Object param) {
						double r = 1, g = 1, b = 1, a = 1;
						int xy = (Integer) particle.getAttribute("x") * (Integer) particle.getAttribute("y");
						int yz = (Integer) particle.getAttribute("y") * (Integer) particle.getAttribute("z");
						int xz = (Integer) particle.getAttribute("x") * (Integer) particle.getAttribute("z");
						int beatNumber = (int) (time / BEAT_PERIOD);
						double beat = TWO_PI * time / BEAT_PERIOD;
						double cos = PMath.cos(beat);
						double cos2x = PMath.cos(2 * beat);

						int xyMod = (xy + beatNumber) % 3;
						switch (xyMod) {
							case 0:
								r = 0.5 + (0.25 * cos);
								g = 0.1 + (0.1 * cos);
								b = 0.5 + (0.25 * cos2x);
								a = 1.0;
								break;

							case 1:
								r = 0.1 + (0.1 * cos);
								g = 0.5 + (0.25 * cos);
								b = 0.5 + (0.25 * cos2x);
								a = 1.0;
								break;

							case 2:
								r = 0.5 + (0.25 * cos);
								g = 0.5 + (0.25 * cos);
								b = 0.1 + (0.1 * cos2x);
								a = 1.0;
								break;
						}

						return new PColor(r, g, b, a);
					}
				});

				addBehavior(Behavior.SIZE, new Behavior(FIRST_VOCALS, null) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 size = (DPoint3) param;
						double siz = power / 8;
						return size.mul(new DPoint3(siz, siz, siz));
					}
				});

				addBehavior(Behavior.ORIENTATION, new Behavior(FIRST_VOCALS, null) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 orientation = (DPoint3) param;
						double rot = (Math.PI / 8 * power);
						return orientation.add(new DPoint3(0, rot, 0));
					}
				});

				addBehavior(Behavior.POSITION, new Behavior(DEEP_THUMP, null) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 position = (DPoint3) param;
						double dx = 0, dy = 0, dz = 0;
						int x = (Integer) particle.getAttribute("x");
						int y = (Integer) particle.getAttribute("y");
						int z = (Integer) particle.getAttribute("z");
						int sum = x + y + z;
						int xy = x * y;
						int yz = y * z;
						int xz = z * z;
						int beatNumber = (int) (time / BEAT_PERIOD);
						double beat = TWO_PI * time / BEAT_PERIOD;
						double beat2 = 2 * TWO_PI * time / BEAT_PERIOD;
						double sin = PMath.sin(beat);
						double cos = PMath.cos(beat);
						double sin2x = PMath.sin(beat2);
						double cos2x = PMath.cos(beat2);
						double sin4 = PMath.sin(beat / 4);
						double cos4 = PMath.cos(beat / 4);

						double mag = 5 * power;
						int mod = sum % 3;
						switch (mod) {
							case 0:
								dx = mag * cos;
								dy = mag * sin;
								break;

							case 1:
								dy = mag * cos;
								dz = mag * sin;
								break;

							case 2:
								dx = mag * sin;
								dz = mag * cos;
								break;
						}

						return position.add(new DPoint3(dx, dy, dz));
					}
				});

				addBehavior(Behavior.POSITION, new Behavior(FERGIE_SOLO, GUITAR_RIFF) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 pos = (DPoint3) param;
						double dt = this.getBehaviorTimeFraction(particle, time);
						Integer x = (Integer) particle.getAttribute("x");
						Integer y = (Integer) particle.getAttribute("y");
						Integer z = (Integer) particle.getAttribute("z");
						int xz = x * z;
						int sum = x + y + z;
						double beat = TWO_PI * time / BEAT_PERIOD;
						double beat2 = 2 * TWO_PI * time / BEAT_PERIOD;
						double sin = PMath.sin(beat);
						double sin2 = PMath.sin(beat2);

						double mag = 10 * power;
						int xzMod = xz % 3;
						double dx = 0, dy = 0, dz = 0;
						switch (xzMod) {
							case 0:
								dx = mag * sin;
								break;

							case 1:
								dy = mag * sin2 / 2;
								break;

							case 2:
								dz = mag * sin;
								break;
						}

						double bdx = 200 * sin(2 * TWO_PI * dt);
						double bdy = 200 * sin(4 * TWO_PI * dt);
						double bdz = 200 * sin(6 * TWO_PI * dt);
						switch (sum % 3) {
							case 0:
								bdx = -bdx;
								break;

							case 1:
								bdy = -bdy;
								break;

							case 2:
								bdz = -bdz;
								break;
						}

						dx += bdx;
						dy += bdy;
						dz += bdz;

						return pos.add(new DPoint3(dx, dy, dz));
					}
				});

				addBehavior(Behavior.POSITION, new Behavior(TONITES_THE_NIGHT, LETS_DO_IT_AGAIN) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 pos = (DPoint3) param;
						double dt = this.getBehaviorTimeFraction(particle, time);
						Integer x = (Integer) particle.getAttribute("x");
						Integer y = (Integer) particle.getAttribute("y");
						Integer z = (Integer) particle.getAttribute("z");
						int xz = x * z;
						int sum = x + y + z;
						double beat = TWO_PI * time / BEAT_PERIOD;
						double beat2 = 2 * TWO_PI * time / BEAT_PERIOD;
						double sin = PMath.sin(beat);
						double sin2 = PMath.sin(beat2);

						double mag = 10 * power;
						int xzMod = xz % 3;
						double dx = 0, dy = 0, dz = 0;
						switch (xzMod) {
							case 0:
								dx = mag * sin;
								break;

							case 1:
								dy = mag * sin;
								break;

							case 2:
								dz = mag * sin;
								break;
						}

						double bdx = 400 * sin(2 * TWO_PI * dt);
						if (sum % 3 == 0) {
							bdx = -bdx;
						}
						double bdy = 400 * sin(4 * TWO_PI * dt);
						if (sum % 3 == 1) {
							bdy = -bdy;
						}
						double bdz = 400 * sin(6 * TWO_PI * dt);
						if (sum % 3 == 2) {
							bdz = -bdz;
						}

						dx += bdx;
						dy += bdy;
						dz += bdz;

						return pos.add(new DPoint3(dx, dy, dz));
					}
				});

				addBehavior(Behavior.POSITION, new Behavior(HERE_WE_COME, AND_IM_FEELING) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 pos = (DPoint3) param;
						double dt = this.getBehaviorTimeFraction(particle, time);
						Integer x = (Integer) particle.getAttribute("x");
						Integer y = (Integer) particle.getAttribute("y");
						Integer z = (Integer) particle.getAttribute("z");
						int xz = x * z;
						int sum = x + y + z;
						double beat = TWO_PI * time / BEAT_PERIOD;
						double beat2 = 2 * TWO_PI * time / BEAT_PERIOD;
						double sin = PMath.sin(beat);
						double sin2 = PMath.sin(beat2);

						double mag = 10 * power;
						int xzMod = xz % 3;
						double dx = 0, dy = 0, dz = 0;
						switch (xzMod) {
							case 0:
								dx = mag * sin;
								break;

							case 1:
								dy = mag * sin;
								break;

							case 2:
								dz = mag * sin;
								break;
						}

						double bdx = 600 * sin(2 * TWO_PI * dt);
						if (sum % 3 == 0) {
							bdx = -bdx;
						}
						double bdy = 600 * sin(4 * TWO_PI * dt);
						if (sum % 3 == 1) {
							bdy = -bdy;
						}
						double bdz = 600 * sin(6 * TWO_PI * dt);
						if (sum % 3 == 2) {
							bdz = -bdz;
						}

						dx += bdx;
						dy += bdy;
						dz += bdz;

						return pos.add(new DPoint3(dx, dy, dz));
					}
				});

			}
		};
		renderers.add(pyramidActor);
		addListener(LIVE_AVERAGE, pyramidActor);

		Cube cube = new Cube(this) {
			private double power = 0;


			public void setPower(double power) {
				this.power = power;
			}


			public void receive(Event event) {
				if (event.getChannel().equals(LIVE_AVERAGE_RMS)) {
					power = ((FloatScalar) event).getValue();
				}
			}


			public void setup() {

				addBehavior(Behavior.ORIENTATION, new Behavior(null, FIRST_STRINGS) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 orientation = (DPoint3) param;
						double dt = this.getBehaviorTimeFraction(particle, time);
						double rot = dt * (Math.PI / 8 * power);
						return orientation.add(new DPoint3(rot, rot, 0));
					}
				});

				addBehavior(Behavior.ORIENTATION, new Behavior(FIRST_STRINGS, FIRST_VOCALS) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 orientation = (DPoint3) param;
						double dt = this.getBehaviorTimeFraction(particle, time);
						double rot = (Math.PI / 8 * power) + (dt * (Math.PI / 8 * power));
						return orientation.add(new DPoint3(rot, rot, 0));
					}
				});

				addBehavior(Behavior.ORIENTATION, new Behavior(FIRST_VOCALS, null) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 orientation = (DPoint3) param;
						double rot = 2 * (Math.PI / 8 * power);
						return orientation.add(new DPoint3(rot, rot, 0));
					}
				});

				addBehavior(Behavior.SIZE, new Behavior(null, FIRST_STRINGS) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 size = (DPoint3) param;
						double siz = 2 * (power / (2 * ROW_SIZE));
						return size.mul(new DPoint3(siz, siz, siz));
					}
				});

				addBehavior(Behavior.SIZE, new Behavior(FIRST_STRINGS, FIRST_VOCALS) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 size = (DPoint3) param;
						double siz = 2 * (power / (2 * ROW_SIZE));
						return size.mul(new DPoint3(siz, siz, siz));
					}
				});

				addBehavior(Behavior.SIZE, new Behavior(FIRST_VOCALS, null) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 size = (DPoint3) param;
						double siz = 2 * (power / (2 * ROW_SIZE));
						return size.mul(new DPoint3(siz, siz, siz));
					}
				});

				addBehavior(Behavior.POSITION, new Behavior(FIRST_VOCALS, DEEP_THUMP) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 pos = (DPoint3) param;
						double dt = this.getBehaviorTimeFraction(particle, time);
						int xz = (Integer) particle.getAttribute("x") * (Integer) particle.getAttribute("z");
						double beat = TWO_PI * time / BEAT_PERIOD;
						double beat2 = 2 * TWO_PI * time / BEAT_PERIOD;
						double sin = PMath.sin(beat);
						double sin2 = PMath.sin(beat2);

						double mag = 10 * power * dt;
						int xzMod = xz % 3;
						double dx = 0, dy = 0, dz = 0;
						switch (xzMod) {
							case 0:
								dx = mag * sin;
								break;

							case 1:
								dy = mag * sin2 / 2;
								break;

							case 2:
								dz = mag * sin;
								break;
						}
						return pos.add(new DPoint3(dx, dy, dz));
					}
				});

				addBehavior(Behavior.POSITION, new Behavior(DEEP_THUMP, null) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 pos = (DPoint3) param;
						int xz = (Integer) particle.getAttribute("x") * (Integer) particle.getAttribute("z");
						double beat = TWO_PI * time / BEAT_PERIOD;
						double beat2 = 2 * TWO_PI * time / BEAT_PERIOD;
						double sin = PMath.sin(beat);
						double sin2 = PMath.sin(beat2);

						double mag = 10 * power;
						int xzMod = xz % 3;
						double dx = 0, dy = 0, dz = 0;
						switch (xzMod) {
							case 0:
								dx = mag * sin;
								break;

							case 1:
								dy = mag * sin2 / 2;
								break;

							case 2:
								dz = mag * sin;
								break;
						}
						return pos.add(new DPoint3(dx, dy, dz));
					}
				});

				addBehavior(Behavior.POSITION, new Behavior(FIRST_CH, GUITAR_RIFF) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 pos = (DPoint3) param;
						double dt = this.getBehaviorTimeFraction(particle, time);
						Integer x = (Integer) particle.getAttribute("x");
						Integer y = (Integer) particle.getAttribute("y");
						Integer z = (Integer) particle.getAttribute("z");
						int xz = x * z;
						int sum = x + y + z;
						double beat = TWO_PI * time / BEAT_PERIOD;
						double beat2 = 2 * TWO_PI * time / BEAT_PERIOD;
						double sin = PMath.sin(beat);
						double sin2 = PMath.sin(beat2);

						double mag = 10 * power;
						int xzMod = xz % 3;
						double dx = 0, dy = 0, dz = 0;
						switch (xzMod) {
							case 0:
								dx = mag * sin;
								break;

							case 1:
								dy = mag * sin2 / 2;
								break;

							case 2:
								dz = mag * sin;
								break;
						}

						double bdx = 200 * sin(2 * TWO_PI * dt);
						if (sum % 3 == 0) {
							bdx = -bdx;
						}
						double bdy = 200 * sin(4 * TWO_PI * dt);
						if (sum % 3 == 1) {
							bdy = -bdy;
						}
						double bdz = 200 * sin(6 * TWO_PI * dt);
						if (sum % 3 == 2) {
							bdz = -bdz;
						}

						dx += bdx;
						dy += bdy;
						dz += bdz;

						return pos.add(new DPoint3(dx, dy, dz));
					}
				});

				addBehavior(Behavior.POSITION, new Behavior(TONITES_THE_NIGHT, LETS_DO_IT_AGAIN) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 pos = (DPoint3) param;
						double dt = this.getBehaviorTimeFraction(particle, time);
						Integer x = (Integer) particle.getAttribute("x");
						Integer y = (Integer) particle.getAttribute("y");
						Integer z = (Integer) particle.getAttribute("z");
						int xz = x * z;
						int sum = x + y + z;
						double beat = TWO_PI * time / BEAT_PERIOD;
						double beat2 = 2 * TWO_PI * time / BEAT_PERIOD;
						double sin = PMath.sin(beat);
						double sin2 = PMath.sin(beat2);

						double mag = 10 * power;
						int xzMod = xz % 3;
						double dx = 0, dy = 0, dz = 0;
						switch (xzMod) {
							case 0:
								dx = mag * sin;
								break;

							case 1:
								dy = mag * sin;
								break;

							case 2:
								dz = mag * sin;
								break;
						}

						double bdx = 400 * sin(2 * TWO_PI * dt);
						if (sum % 3 == 0) {
							bdx = -bdx;
						}
						double bdy = 400 * sin(4 * TWO_PI * dt);
						if (sum % 3 == 1) {
							bdy = -bdy;
						}
						double bdz = 400 * sin(6 * TWO_PI * dt);
						if (sum % 3 == 2) {
							bdz = -bdz;
						}

						dx += bdx;
						dy += bdy;
						dz += bdz;

						return pos.add(new DPoint3(dx, dy, dz));
					}
				});

				addBehavior(Behavior.POSITION, new Behavior(HERE_WE_COME, AND_IM_FEELING) {
					public Object getValue(Particle particle, Double time, Object param) {
						DPoint3 pos = (DPoint3) param;
						double dt = this.getBehaviorTimeFraction(particle, time);
						Integer x = (Integer) particle.getAttribute("x");
						Integer y = (Integer) particle.getAttribute("y");
						Integer z = (Integer) particle.getAttribute("z");
						int xz = x * z;
						int sum = x + y + z;
						double beat = TWO_PI * time / BEAT_PERIOD;
						double beat2 = 2 * TWO_PI * time / BEAT_PERIOD;
						double sin = PMath.sin(beat);
						double sin2 = PMath.sin(beat2);

						double mag = 10 * power;
						int xzMod = xz % 3;
						double dx = 0, dy = 0, dz = 0;
						switch (xzMod) {
							case 0:
								dx = mag * sin;
								break;

							case 1:
								dy = mag * sin;
								break;

							case 2:
								dz = mag * sin;
								break;
						}

						double bdx = 600 * sin(2 * TWO_PI * dt);
						if (sum % 3 == 0) {
							bdx = -bdx;
						}
						double bdy = 600 * sin(4 * TWO_PI * dt);
						if (sum % 3 == 1) {
							bdy = -bdy;
						}
						double bdz = 600 * sin(6 * TWO_PI * dt);
						if (sum % 3 == 2) {
							bdz = -bdz;
						}

						dx += bdx;
						dy += bdy;
						dz += bdz;

						return pos.add(new DPoint3(dx, dy, dz));
					}
				});

				addBehavior(Behavior.FILL_COLOR, new Behavior(null, null) {
					public Object getValue(Particle particle, Double time, Object param) {
						double siz = power / 8;
						return new PColor(0.3, 0, siz, 1);
					}
				});

				addBehavior(Behavior.LINE_COLOR, new Behavior(FIRST_BASS, null) {
					public Object getValue(Particle particle, Double time, Object param) {
						double r = 1, g = 1, b = 1, a = 1;
						int xy = (Integer) particle.getAttribute("x") * (Integer) particle.getAttribute("y");
						int yz = (Integer) particle.getAttribute("y") * (Integer) particle.getAttribute("z");
						int xz = (Integer) particle.getAttribute("x") * (Integer) particle.getAttribute("z");
						int beatNumber = (int) (time / BEAT_PERIOD);
						double beat = TWO_PI * time / BEAT_PERIOD;
						double cos = PMath.cos(beat);
						double cos2x = PMath.cos(2 * beat);

						int xyMod = (xy + beatNumber) % 3;
						switch (xyMod) {
							case 0:
								r = 0.5 + (0.25 * cos);
								g = 0.1 + (0.1 * cos);
								b = 0.5 + (0.25 * cos2x);
								a = 1.0;
								break;

							case 1:
								r = 0.1 + (0.1 * cos);
								g = 0.5 + (0.25 * cos);
								b = 0.5 + (0.25 * cos2x);
								a = 1.0;
								break;

							case 2:
								r = 0.5 + (0.25 * cos);
								g = 0.5 + (0.25 * cos);
								b = 0.1 + (0.1 * cos2x);
								a = 1.0;
								break;
						}

						return new PColor(r, g, b, a);
					}
				});

			}
		};
		renderers.add(cube);
		addListener(LIVE_AVERAGE_RMS, cube);

		space.setSetup(new Space.Action(space) {
			public void action() {
				Particle particle;

				double pSize = PARTICLE_SIZE;
				int dd = ROW_SIZE;
				int countX = dd - 1;
				int countY = dd - 1;
				int countZ = dd - 1;
				double extent = EXTENT;
				double step = extent / dd;
				int i = 0;
				for (int x = 0; x <= countX; x++) {
					for (int y = 0; y <= countY; y++) {
						for (int z = 0; z <= countZ; z++) {
							double px = (x * step) - extent / 2;
							double py = (y * step) - extent / 2;
							double pz = (z * step) - extent / 2;

							particle = new Particle(new DPoint3(px, py, pz));
							particle.setSize(new DPoint3(pSize, pSize, pSize));
							particle.setFillColor(PColor.NONE);
							particle.setLineColor(PColor.WHITE);
							particle.setActor(renderers.get(i % renderers.size()));

							particle.setAttribute("x", x);
							particle.setAttribute("y", y);
							particle.setAttribute("z", z);

							space.addParticle(particle);

							i++;
						}
					}
				}
			}
		});
	}


	public void drawDynamic() {
		smooth();
		setCamera();
		setLighting();
		setStyle();

		space.run();

//		if (frameCount % 50 == 0) {
//			System.out.println("Time: " + getTime());
//			System.out.println("Frame rate: " + frameRate);
//			System.out.println("Particles: " + space.getParticles().size());
//		}
	}


	public void setStyle() {
		background(0);
		noFill();
		ellipseMode(CENTER);
		stroke(PColor.WHITE);
	}


	public void setLighting() {
		directionalLight(.5f, .5f, .5f, 1, 1, -1);
		ambientLight(.25f, .25f, .25f);
		specular(.5);
	}


	public void setCamera() {
		double t = getTime();

		double maxDistance = 3000;
		double normDistance = 1700;

		double distance = maxDistance;
		double beatPeriod = 112d * BEAT_PERIOD;

		if (LOCK_CAMERA) {
			camera(0, 0, distance, 0, 0, 0, 0f, -1f, 0f);
		}
		else {
			if (t < FIRST_STRINGS) {
				distance = maxDistance;
				spinCamera(new DPoint3(0, 0, 700), new DPoint3(distance, distance, distance), new DPoint3(), beatPeriod);
			}
			else if ((t >= FIRST_STRINGS) && (t < FIRST_VOCALS)) {
				double dt = (t - FIRST_STRINGS) / (FIRST_VOCALS - FIRST_STRINGS);
				distance = maxDistance - (dt * (maxDistance - normDistance));
				spinCamera(new DPoint3(0, 0, 700), new DPoint3(distance, distance, distance), new DPoint3(), beatPeriod);
			}
			else {
				distance = normDistance;
				spinCamera(new DPoint3(0, 0, 700), new DPoint3(distance, distance, distance), new DPoint3(), beatPeriod);
			}
		}
	}
}